home *** CD-ROM | disk | FTP | other *** search
- # PCGIPublisher.py
- # Persistent CGI Publisher - jeffbauer@bigfoot.com
- #
- # Copyright (c) 1998, Digital Creations, Fredericksburg, VA, USA. All
- # rights reserved. This software includes contributions from Jeff Bauer.
- #
- # Redistribution and use in source and binary forms, with or without
- # modification, are permitted provided that the following conditions are
- # met:
- #
- # o Redistributions of source code must retain the above copyright
- # notice, this list of conditions, and the disclaimer that follows.
- #
- # o Redistributions in binary form must reproduce the above copyright
- # notice, this list of conditions, and the following disclaimer in
- # the documentation and/or other materials provided with the
- # distribution.
- #
- # o All advertising materials mentioning features or use of this
- # software must display the following acknowledgement:
- #
- # This product includes software developed by Digital Creations
- # and its contributors.
- #
- # o Neither the name of Digital Creations nor the names of its
- # contributors may be used to endorse or promote products derived
- # from this software without specific prior written permission.
- #
- #
- # THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS *AS IS* AND
- # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
- # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS
- # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
- # BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
- # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
- # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
- # IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- #
- # 2.0 alpha4:
- # - adds NT support, using INET sockets
- # - added Mike Fletcher's Microsoft IIS hack, suggested by Amos Latteier
- #
- # 2.0 alpha3:
- # - adds error checking
- # - expands header to 10 bytes (extra byte defines an out-of-band msg)
- #
- # 2.0 alpha1:
- # - add support for new PCGI_ directives
- # - re-write module as a class, rather than a set of functions
-
- __version__ = "2.0a4"
-
- import string, sys, os
-
- class PCGIPublisher:
- def __init__(self, resource=None):
- ### resources passed from the environment, info file or pcgi-wrapper
- self.errorLogFile = None
- self.hostname = None
- self.port = 0
- self.insertPath = None
- self.moduleName = None
- self.modulePath = None
- self.socketFile = None
- self.pidFile = None
- self.swhome = None
- self.sock = None
-
- ### bound imports ###
- self.StringIO = None
- self.publish_module = None
-
- ### other ###
- self.bufsize = 8192
- self.error = 0
-
- if resource is None:
- self.getResources()
- else:
- self.resource = resource
- self.initPCGI()
-
- def cleanup(self):
- if self.socketFile:
- import os
- if os.path.exists(self.socketFile):
- try:
- os.unlink(self.socketFile)
- except os.error:
- pass
-
- def getResources(self):
- """
- Obtain the publisher resources from the environment. Done here
- in a separate method to make it easy to override, because we may
- not always and forever obtain our resources from the environment.
- """
- import os
- self.resource = os.environ
-
- def fatalError(self, errmsg=''):
- if self.errorLogFile:
- import sys, traceback, StringIO
- try:
- from time import asctime, localtime, time
- timeStamp = asctime(localtime(time()))
- except ImportError:
- timeStamp = '???'
- try:
- f = open(self.errorLogFile, 'a+')
- f.write("%s %s\n" % (timeStamp, errmsg))
- if sys.exc_type != SystemExit:
- trace=StringIO.StringIO()
- traceback.print_exception(sys.exc_type,
- sys.exc_value,
- sys.exc_traceback,
- None,
- trace)
- f.write(" %s\n" % trace.getvalue())
- f.close()
- except IOError:
- pass
- self.cleanup()
- self.error = 1
-
- def initPCGI(self):
- import os
- self.initPrincipia()
- if self.resource.has_key('PCGI_ERROR_LOG'):
- self.errorLogFile = self.resource['PCGI_ERROR_LOG']
- if self.resource.has_key('PCGI_HOST'):
- self.hostname = self.resource['PCGI_HOST']
- if self.resource.has_key('PCGI_INSERT_PATH'):
- self.insertPath = self.resource['PCGI_INSERT_PATH']
- if self.resource.has_key('PCGI_MODULE_PATH'):
- self.modulePath = self.resource['PCGI_MODULE_PATH']
- if self.resource.has_key('PCGI_NAME'):
- self.moduleName = self.resource['PCGI_NAME']
- if self.resource.has_key('PCGI_PID_FILE'):
- self.pidFile = self.resource['PCGI_PID_FILE']
- if self.resource.has_key('PCGI_PORT'):
- import string
- try: self.port = string.atoi(self.resource['PCGI_PORT'])
- except ValueError: pass
- if self.resource.has_key('PCGI_SOCKET_FILE'):
- self.socketFile = self.resource['PCGI_SOCKET_FILE']
- self.insertSysPath()
-
- if not self.moduleName:
- return self.fatalError("missing module name, try specifying PCGI_NAME")
-
- ### TODO: probably should make an attempt to import self.moduleName
- ### to provide the user with earliest possible response.
-
- try:
- from cStringIO import StringIO
- except:
- from StringIO import StringIO
- self.StringIO = StringIO
-
- try:
- from ZPublisher import publish_module
- except ImportError:
- try:
- from cgi_module_publisher import publish_module
- except ImportError:
- return self.fatalError(
- "unable to import publish_module from ZPublisher")
-
- self.publish_module = publish_module
-
- if not self.pidFile:
- return self.fatalError("missing pid file")
-
- ### create pid file ###
- try:
- f = open(self.pidFile, 'wb')
- f.write(str(os.getpid()))
- f.close()
- except IOError:
- return self.fatalError("unable to write to pid file: %s" % self.pidFile)
- import socket
- if not self.socketFile:
- return self.fatalError("missing socket file")
- if self.port:
- if os.sep == '/':
- return self.fatalError("INET sockets not yet available on Unix")
- if self.hostname is None:
- self.hostname = socket.gethostname()
- try:
- self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- self.sock.bind((self.hostname, self.port))
- except socket.error:
- return self.fatalError("error binding to socket: %s" % self.socketFile)
- try:
- sf = open(self.socketFile, 'wb')
- sf.write("%s\n%s\n" % (self.hostname, self.port))
- sf.close()
- except IOError:
- return self.fatalError("error attempting to write socket file: %s" % self.socketFile)
- else:
- try: os.unlink(self.socketFile)
- except os.error: pass
- try:
- self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
- self.sock.bind(self.socketFile)
- except socket.error:
- return self.fatalError("error binding to socket: %s" % self.socketFile)
- try: os.chmod(self.socketFile, 0777)
- except os.error: pass
-
- def initPrincipia(self):
- if self.resource.has_key('SOFTWARE_NAME'):
- self.moduleName = self.resource['SOFTWARE_NAME']
-
- def insertSysPath(self, insertPath=None):
- import os, sys, string
- if insertPath is None:
- insertPath = self.insertPath
- if insertPath:
- sys.path[0:0]=string.split(insertPath,':')
- elif self.resource.has_key('PCGI_WORKING_DIR'):
- ### Note: PCGI_WORKING_DIR is a deprecated pcgi directive ###
- workDir = self.resource['PCGI_WORKING_DIR']
- while workDir[-1:]=='/' or workDir[-1:]=='\\':
- workDir=workDir[:-1]
- sys.path[0:0]=[workDir]
- else:
- pass
-
- # The present assumption is that if the module path isn't in
- # sys.path, we want it put there, even if the PCGI_INSERT_PATH
- # directive has been specified.
- if self.modulePath:
- d, s = os.path.split(self.modulePath)
- if not d in sys.path:
- sys.path[0:0] = [d]
- # If the moduleName is not known at this time, we make a
- # reasonable guess based on the path. It might behoove the
- # user to explicitly specify PCGI_NAME, however.
- if not self.moduleName:
- import string
- self.moduleName = string.splitfields(s,'.')[0]
-
- def handler(self, conn):
- from string import split, join, atoi
- hdr = conn.recv(10)
-
- size = atoi(hdr)
- buff = []
- while size > 0:
- data = conn.recv(size)
- size = size - len(data)
- buff.append(data)
-
- ### XXX - Later: add out-of-band data handling ###
- if (hdr[0] != '0'):
- return
-
- env = {}
- for i in filter(None, split(join(buff,''),'\000')):
- e = split(i,'=')
- if len(e) >= 2:
- env[e[0]] = join(e[1:],'=')
- else:
- env[e[0]]=''
- size = atoi(conn.recv(10))
- if size > 1048576:
- ### write large upload data to a file ###
- from tempfile import TemporaryFile
- stdin = TemporaryFile('w+b')
- bufsize = self.bufsize
- while size > 0:
- if size < bufsize:
- bufsize=size
- data = conn.recv(bufsize)
- size = size - len(data)
- stdin.write(data)
- stdin.seek(0,0)
- else:
- ### use StringIO for smaller data ###
- buff = []
- while size > 0:
- data = conn.recv(size)
- size = size-len(data)
- buff.append(data)
- stdin = self.StringIO(join(buff,''))
- stdout = self.StringIO()
- stderr = self.StringIO()
-
- ### IIS hack to fix broken PATH_INFO
- ### taken from Mike Fletcher's win_cgi_module_publisher
- import string
- if env.has_key('SERVER_SOFTWARE') and string.find(env['SERVER_SOFTWARE'],'Microsoft-IIS') != -1:
- script = filter(None,string.split(string.strip(env['SCRIPT_NAME']),'/'))
- path = filter(None,string.split(string.strip(env['PATH_INFO']),'/'))
- env['PATH_INFO'] = string.join(path[len(script):],'/')
-
- try:
- self.publish_module(self.moduleName,stdin=stdin,stdout=stdout,stderr=stderr,environ=env)
- except:
- self.fatalError("unable to publish module")
-
- stdin.close()
- stdout=stdout.getvalue()
- stderr=stderr.getvalue()
-
- stdout_len=len(stdout)
- conn.send('%010d' % len(stdout))
- to_send=stdout_len
- if to_send > 0:
- while 1:
- sent = conn.send(stdout)
- if sent == to_send:
- break
- else:
- to_send = to_send - sent
- stdout = stdout[sent:]
-
- stderr_len=len(stderr)
- conn.send('%010d' % stderr_len)
- to_send=stderr_len
- if to_send > 0:
- while 1:
- sent = conn.send(stderr)
- if sent == to_send:
- break
- else:
- to_send = to_send - sent
- stderr = stderr[sent:]
-
- conn.close()
-
- def listen(self):
- """
- Note to sub-classes: Aside from the constructor, listen() should
- be the only method you *must* invoke.
- """
- if self.error:
- return self.fatalError("attempt to listen after fatal error")
-
- import os, sys
- if not self.sock:
- return self.fatalError("no socket available")
-
- self.sock.listen(512)
-
- stdlog('stderr')
- stdlog('stdout')
-
- while not self.error:
- conn, accept = self.sock.accept()
- try:
- self.handler(conn)
- except socket.error:
- pass
-
- def stdlog(name):
- NAME=string.upper(name)+'_LOG'
- if os.environ.has_key(NAME):
- f=os.environ[NAME]
- else:
- f='/dev/null'
- try: f=open(f,'a')
- except: f=None
- getattr(sys, name).close()
- if f is not None: setattr(sys, name, f)
-
- def main():
- try:
- import os, sys, string, traceback
- pcgiPublisher = PCGIPublisher(os.environ)
- if not pcgiPublisher.error:
- pcgiPublisher.listen()
- except ImportError:
- print "Content-type: text/html"
- print
- print "PCGIPublisher catastrophic import error"
-
- if __name__ == '__main__':
- try:
- main()
- finally:
- import os
- if os.environ.has_key('PCGI_SOCKET_FILE'):
- try: os.unlink(os.environ['PCGI_SOCKET_FILE'])
- except os.error: pass
-